FIFO Queues In .NET
2 min readFIFO Queues In .NET
TL;DR
⚙️ Threading Tip: Prefer Channel<T> or System.Threading.Channels when you need async waiting rather than spin-checking a Queue<T>.
How it works
- Definition: First-In, First-Out data structure where the earliest enqueued item is dequeued first.
- API Surface: Use
Queue<T>for in-memory work,Channel<T>for async producers/consumers, andConcurrentQueue<T>in multi-threaded scenarios.
var orders = new Queue<Order>();
orders.Enqueue(new Order("EURUSD", 1_000_000m));
orders.Enqueue(new Order("GBPUSD", 750_000m));
while (orders.TryDequeue(out var next))
{
await _executionService.ExecuteAsync(next);
}
Quick recall Q&A
Queue<T> vs ConcurrentQueue<T>?Use Queue<T> for single-threaded scenarios. Use ConcurrentQueue<T> when multiple threads enqueue/dequeue concurrently, but note it lacks blocking reads.
Channel<T> over BlockingCollection<T>?Channel<T> supports async producers/consumers, backpressure, and high performance without legacy BlockingCollection overhead. It integrates well with async/await.
Use bounded Channel<T> so producers await when the queue is full, preventing memory blowups and throttling upstream systems.
Single consumer preserves strict FIFO. If you scale out, partition by key (e.g., account ID) so each partition maintains order, or accept eventual ordering per partition only.
Use durable queues (RabbitMQ, Azure Service Bus) with FIFO support, or persist queue state in a database/outbox to resume processing after failure.
Queue<T>.Peek() is O(1) and non-destructive. For channels, peeking isn’t supported; you’d need to buffer manually if you must inspect before processing.
Queue<T>?Use synchronization primitives (SemaphoreSlim, AutoResetEvent) or switch to Channel<T>/BlockingCollection<T> which provide blocking/async waits.
Track enqueue/dequeue rates, queue length, and processing latency. Alert when queue length grows unexpectedly—indicates downstream slowness.
Implement retries with exponential backoff, move failures to a dead-letter queue, and avoid blocking the FIFO by skipping or quarantining bad entries.
Combine bounded channels with SemaphoreSlim or token buckets. Producers await when the channel is full, smoothing load on downstream services.